/**
 * 
 */
package com.gragra.frameStatistics;
import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import com.gragra.data.Corpus;
import com.gragra.sampling.sequentialStructure.ExtensionInformationHolder;
import com.gragra.sampling.sequentialStructure.ExtensionInformationLevel;
import com.gragra.sampling.sequentialStructure.ExtensionInformationValue;
import com.gragra.sampling.sequentialStructure.ExtensionLevels;
import com.gragra.sampling.sequentialStructure.WordAndTagHolder;
/**
 * @author Christoph Teichmann
 * 28.05.2013
 */
public class LeftAndRightStatistics
{
	/**
	 * 
	 */
	private final Int2ObjectOpenHashMap<Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>> featureCounts = new Int2ObjectOpenHashMap<Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>>();
	/**
	 * 
	 */
	private final Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> left[];
	/**
	 * 
	 */
	private final Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> right[];
	/**
	 * 
	 */
	private final Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> leftTags[];
	/**
	 * 
	 */
	private final Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> rightTags[];
	/**
	 * 
	 */
	private final Int2DoubleOpenHashMap wordCounts = new Int2DoubleOpenHashMap();
	/**
	 * 
	 */
	private final Int2DoubleOpenHashMap tagCounts = new Int2DoubleOpenHashMap();
	/**
	 * 
	 */
	private final Int2IntOpenHashMap frequencyRank = new Int2IntOpenHashMap();
	/**
	 * 
	 */
	private final Int2IntOpenHashMap tagFrequencyRank = new Int2IntOpenHashMap();
	/**
	 * 
	 */
	private final int offset;
	/**
	 * 
	 * @param c
	 * @param usePreMarker
	 * @param usePostMarker
	 * @param useTags
	 */
	@SuppressWarnings("unchecked")
	public LeftAndRightStatistics(Corpus<WordAndTagHolder> c,int offset)
	{
		this.offset = offset;
		this.left = new Int2ObjectOpenHashMap[offset];
		this.right = new Int2ObjectOpenHashMap[offset];
		this.leftTags = new Int2ObjectOpenHashMap[offset];
		this.rightTags = new Int2ObjectOpenHashMap[offset];
		for(int i=0;i<offset;++i)
		{
			this.left[i] = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
			this.right[i] = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
			this.leftTags[i] = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
			this.rightTags[i] = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
		}
		additionalContstruction();
		fillStatistics(c);
	}
	/**
	 * 
	 * @param c
	 */
	private void fillStatistics(Corpus<WordAndTagHolder> c)
	{
		for(WordAndTagHolder wth : c)
		{addCounts(wth);}
		makeRankings();
		additionalFilling(c);
	}
	/**
	 * 
	 */
	protected void additionalFilling(Corpus<WordAndTagHolder> c)
	{}
	/**
	 * 
	 */
	private void makeRankings()
	{
		this.makeRanking(this.tagCounts,this.tagFrequencyRank);
		this.makeRanking(this.wordCounts, this.frequencyRank);
	}
	/**
	 * 
	 * @param counts
	 * @param rankHolder
	 */
	private void makeRanking(Int2DoubleOpenHashMap counts,
			Int2IntOpenHashMap rankHolder)
	{
		Double2ObjectRBTreeMap<IntArrayList> sorter = new Double2ObjectRBTreeMap<IntArrayList>();
		IntIterator iit = counts.keySet().iterator();
		while(iit.hasNext())
		{
			int i = iit.nextInt();
			double d = counts.get(i);
			this.add(sorter,i,-d);
		}
		int i = 0;
		for(IntArrayList il : sorter.values())
		{
			iit = il.iterator();
			while(iit.hasNext())
			{
				rankHolder.put(iit.nextInt(), i++);
			}
		}
	}
	/**
	 * 
	 * @param sorter
	 * @param i
	 * @param d
	 */
	private void add(Double2ObjectRBTreeMap<IntArrayList> sorter, int i,
			double d)
	{
		IntArrayList ial = sorter.get(d);
		if(ial == null)
		{
			ial = new IntArrayList();
			sorter.put(d, ial);
		}
		ial.add(i);
	}
	/**
	 * 
	 * @param wth
	 */
	private void addCounts(WordAndTagHolder wth)
	{
		fillWordsAndMapped(wth);
		addEntries(wth);
	}
	/**
	 * 
	 */
	private void addEntries(WordAndTagHolder wth)
	{
		int len = this.mapped.size()-offset;
		ExtensionInformationHolder eih  = wth.getExtensionInformation();
		for(int i=this.offset;i<len;++i)
		{
			int word = this.words.getInt(i);
			int tag = this.mapped.getInt(i);
			this.add(this.wordCounts,word);
			this.add(this.tagCounts, tag);
			this.addExtensionCounts(eih.getFiniteInformationAt(i-this.offset),eih.getInfiniteInformationAt(i-this.offset),word);
			for(int k=0;k<this.offset;++k)
			{
				int leftWord = this.words.getInt(i-k-1);
				int leftTag = this.mapped.getInt(i-k-1);
				this.add(this.left, word,leftWord,k);
				this.add(this.leftTags, word, leftTag,k);
			}
			for(int k=0;k<this.offset;++k)
			{
				int rightWord = this.words.getInt(i+1+k);
				int rightTag = this.mapped.getInt(i+1+k);
				this.add(this.right, word,rightWord,k);
				this.add(this.rightTags, word, rightTag,k);
			}
		}
	}
	/**
	 * 
	 * @param finiteInformationAt
	 * @param infiniteInformationAt
	 * @param word
	 */
	private void addExtensionCounts(
			Object2ObjectOpenHashMap<ExtensionInformationLevel, ExtensionInformationValue> finiteInformationAt,
			Object2IntOpenHashMap<ExtensionInformationLevel> infiniteInformationAt,int word)
	{
		Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> countsForWord = this.featureCounts.get(word);
		if(countsForWord == null)
		{
			countsForWord = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
			this.featureCounts.put(word, countsForWord);
		}
		for(ExtensionInformationLevel eil : finiteInformationAt.keySet())
		{
			if(!(eil instanceof ExtensionLevels))
			{continue;}
			int num = ((ExtensionLevels) eil).getNumber();
			Int2DoubleOpenHashMap m = countsForWord.get(num);
			if(m == null)
			{
				m = new Int2DoubleOpenHashMap();
				countsForWord.put(num, m);
			}
			ExtensionInformationValue eiv = finiteInformationAt.get(eil);
			m.addTo(eiv.getNumber(), 1.0);
		}
		for(ExtensionInformationLevel eil : infiniteInformationAt.keySet())
		{
			if(!(eil instanceof ExtensionLevels))
			{continue;}
			int num = ((ExtensionLevels) eil).getNumber();
			Int2DoubleOpenHashMap m = countsForWord.get(num);
			if(m == null)
			{
				m = new Int2DoubleOpenHashMap();
				countsForWord.put(num, m);
			}
			m.addTo(infiniteInformationAt.get(eil), 1.0);
		}
	}
	/**
	 * 
	 * @param array
	 * @param key1
	 * @param key2
	 * @param position
	 */
	private void add(Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>[] array,
			int key1, int key2,int position)
	{
		Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> m = array[position];
		this.add(m, key1, key2);
	}
	/**
	 * 
	 * @param left2
	 * @param word
	 * @param leftWord
	 */
	private void add(Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> counter,
			int key1, int key2)
	{
		Int2DoubleOpenHashMap map = counter.get(key1);
		if(map == null)
		{
			map = new Int2DoubleOpenHashMap();
			counter.put(key1, map);
		}
		this.add(map, key2);
	}
	/**
	 * 
	 * @param counter
	 * @param word
	 */
	private void add(Int2DoubleOpenHashMap counter, int key)
	{counter.addTo(key, 1.0);}
	/**
	 * @param wth 
	 * 
	 */
	private void fillWordsAndMapped(WordAndTagHolder wth)
	{
		this.mapped.clear();
		this.words.clear();
		for(int i=0;i<this.offset;++i)
		{
			this.mapped.add(-1);
			this.words.add(-1);
		}
		for(int i=0;i<wth.getLength();++i)
		{
			this.words.add(wth.getWord(i));
			this.mapped.add(wth.getExtensionInformation().getInfiniteInformation(i, ExtensionLevels.ContextTagLevel));
		}
		for(int i=0;i<this.offset;++i)
		{
			this.mapped.add(-1);
			this.words.add(-1);
		}
	}
	/**
	 * 
	 */
	private final IntArrayList mapped = new IntArrayList();
	/**
	 * 
	 */
	private final IntArrayList words = new IntArrayList();
	/**
	 * 
	 */
	protected void additionalContstruction()
	{}
	/**
	 * 
	 * @return
	 */
	public IntSet getAllWords()
	{return this.wordCounts.keySet();}
	/**
	 * 
	 * @return
	 */
	public int getPositions()
	{return this.offset;}
	/**
	 * 
	 * @param word
	 * @param i
	 * @return
	 */
	public IntSet getLeftForWord(int word, int position)
	{
		Int2DoubleOpenHashMap m = this.left[position].get(word);
		return m == null ? null : m.keySet();
	}
	/**
	 * 
	 * @param word
	 * @param i
	 */
	public IntSet getLeftTagForWord(int word, int position)
	{
		Int2DoubleOpenHashMap m = this.leftTags[position].get(word);
		return m == null ? null : m.keySet();
	}
	/**
	 * 
	 * @param word
	 * @param fram
	 * @param position
	 */
	public double getLeftCoCount(int word, int fram, int position)
	{
		Int2DoubleOpenHashMap m = this.left[position].get(word);
		return m == null ? 0.0 : m.get(fram);
	}
	/**
	 * 
	 * @param word
	 * @param fram
	 * @param i
	 * @return
	 */
	public double getLeftCoTagCount(int word, int fram, int position)
	{
		Int2DoubleOpenHashMap m = this.leftTags[position].get(word);
		return m == null ? 0.0 : m.get(fram);
	}
	/**
	 * 
	 * @param fram
	 * @return
	 */
	public int getRank(int word)
	{return this.frequencyRank.get(word);}
	/**
	 * 
	 * @param fram
	 * @return
	 */
	public int getTagRank(int tag)
	{return this.tagFrequencyRank.get(tag);}
	/**
	 * 
	 * @param word
	 * @param i
	 * @return
	 */
	public IntSet getRightForWord(int word, int pos)
	{
		Int2DoubleOpenHashMap m = this.right[pos].get(word);
		return m == null ? null : m.keySet();
	}
	/**
	 * 
	 * @param word
	 * @param i
	 * @return
	 */
	public IntSet getRightTagForWord(int word, int pos)
	{
		Int2DoubleOpenHashMap m = this.rightTags[pos].get(word);
		return m == null ? null : m.keySet();
	}
	/**
	 * 
	 * @param word
	 * @param fram
	 * @param i
	 * @return
	 */
	public double getRightCoCount(int word, int fram, int pos)
	{
		Int2DoubleOpenHashMap m = this.right[pos].get(word);
		return m == null ? 0.0 : m.get(fram);
	}
	/**
	 * 
	 * @param word
	 * @param fram
	 * @param i
	 * @return
	 */
	public double getRightCoTagCount(int word, int fram, int pos)
	{
		Int2DoubleOpenHashMap m = this.rightTags[pos].get(word);
		return m == null ? 0.0 : m.get(fram);
	}
	/**
	 * 
	 * @param word
	 * @return
	 */
	public Int2ObjectOpenHashMap<Int2DoubleOpenHashMap> getFeatureCounts(
			int word)
	{return this.featureCounts.get(word);}
}